home *** CD-ROM | disk | FTP | other *** search
/ Hardcore Visual Basic 5.0 (2nd Edition) / Hardcore Visual Basic 5.0 - Second Edition (1997)(Microsoft Press).iso / Code / SLISTBOX.CLS < prev    next >
Text File  |  1997-06-14  |  16KB  |  614 lines

  1. VERSION 1.0 CLASS
  2. BEGIN
  3.   MultiUse = -1  'True
  4. END
  5. Attribute VB_Name = "CSortedListBox"
  6. Attribute VB_GlobalNameSpace = False
  7. Attribute VB_Creatable = False
  8. Attribute VB_PredeclaredId = False
  9. Attribute VB_Exposed = False
  10. Option Explicit
  11.  
  12. '$ Uses SLISTBOX.BAS UTILITY.BAS
  13.  
  14. Public Enum ESortError
  15.     eseNone = 0
  16.     eseItemNotFound = 1
  17.     eseOutOfRange = 2
  18.     eseDuplicateNotAllowed = 3
  19.     eseNoSelection = 4
  20.     eseNotListBox = 5
  21.     eseAlreadySorted = 6
  22.     eseUnknown = 7
  23. End Enum
  24.  
  25. Private lst As Control
  26. Private esmMode As ESortMode
  27. Private fHiToLo As Boolean
  28.  
  29. Private asError(0 To 7) As String
  30.  
  31. Public LastError As Integer
  32.   
  33. ''' Public Methods Unique to This Class '''
  34.     
  35. ' Create sorted list box from unsorted list box or similar control
  36. Function Create(lstA As Control, Optional fHiToLoA As Boolean = False, _
  37.                 Optional esmModeA As ESortMode = esmUnsorted) As Boolean
  38.     
  39.     ' Must have ListCount, List, ListIndex, AddItem, and RemoveItem
  40.     On Error GoTo CreateFail
  41.     Dim v As Variant
  42.     LastError = eseNotListBox
  43.     v = lstA.ListCount
  44.     lstA.AddItem v, 0
  45.     v = lstA.list(0)
  46.     lstA.list(0) = v
  47.     v = lstA.ListIndex
  48.     lstA.RemoveItem 0
  49.     ' Make sure list isn't already sorted
  50.     LastError = eseAlreadySorted
  51.     If lstA.Sorted Then GoTo CreateFail
  52.         
  53.     ' Initialize internal data
  54.     LastError = eseUnknown
  55.     fHiToLo = fHiToLoA
  56.     esmMode = esmSortVal
  57.     If esmModeA <> -1 Then esmMode = esmModeA
  58.     Set lst = lstA
  59.     ' Sort it
  60.     Sort 0, lst.ListCount - 1
  61.     Create = True
  62.     LastError = eseNone
  63.     Exit Function
  64.     
  65. CreateFail:
  66.     Create = False
  67. End Function
  68.  
  69. ' Note that our AddItem only takes an item. You cannot specify the
  70. ' insert position with a sorted list as you can with an unsorted list.
  71. ' Also, you cannot insert an item that already exists into a sorted
  72. ' list. A request to do so will be ignored.
  73. Sub AddItem(sItem As String)
  74.     LastError = eseNone
  75.     ' Binary search for the item
  76.     Dim iPos As Integer
  77.     If BSearch(sItem, iPos) Then
  78.         LastError = eseDuplicateNotAllowed
  79.     Else
  80.         lst.AddItem sItem, iPos
  81.     End If
  82. End Sub
  83.  
  84. ' RemoveItem takes numeric 0-based item index
  85. Sub RemoveItem(iItem As Integer)
  86.     lst.RemoveItem iItem
  87. End Sub
  88.  
  89. ' Collection Methods
  90. ' Same as AddItem but with collection name (but not extra arguments)
  91. Sub Add(vItem As Variant)
  92.     LastError = eseNone
  93.     ' Binary search for the item
  94.     Dim iPos As Integer
  95.     If BSearch(vItem, iPos) Then
  96.         LastError = eseDuplicateNotAllowed
  97.     Else
  98.         lst.AddItem vItem, iPos - 1
  99.     End If
  100. End Sub
  101.  
  102. ' Same as RemoveItem but has collection name and is 1-based
  103. Sub Remove(vItem As Variant)
  104.     LastError = eseNone
  105.     If IsNumeric(vItem) Then
  106.         If vItem > Count Or vItem < 1 Then LastError = eseOutOfRange: Exit Sub
  107.     Else
  108.         vItem = Match(vItem)
  109.         If vItem = 0 Then LastError = eseItemNotFound: Exit Sub
  110.     End If
  111.     lst.RemoveItem vItem - 1
  112. End Sub
  113.  
  114. ' Similar to List property
  115. Property Get Item(vIndex As Variant) As Variant
  116.     LastError = eseNone
  117.     If IsNumeric(vIndex) Then
  118.         ' For numeric index, return string value
  119.         Item = lst.list(vIndex - 1)
  120.     Else
  121.         ' For string index, return matching index or 0 for none
  122.         Item = Match(vIndex)
  123.         If Item = 0 Then LastError = eseItemNotFound
  124.     End If
  125. End Property
  126.  
  127. Property Let Item(vIndex As Variant, vItemA As Variant)
  128.     LastError = eseNone
  129.     ' For string index, look up matching index
  130.     If Not IsNumeric(vIndex) Then
  131.         vIndex = Match(vIndex)
  132.         ' Quit if old item isn't found or if new item is found
  133.         If vIndex = 0 Then
  134.             LastError = eseItemNotFound
  135.             Exit Property
  136.         End If
  137.         If Match(vItemA) Then
  138.             LastError = eseDuplicateNotAllowed
  139.             Exit Property
  140.         End If
  141.     End If
  142.     ' Assign value by removing old and inserting new
  143.     Remove vIndex
  144.     Add vItemA
  145. End Property
  146.  
  147. ''' Public Properties Unique to This Class '''
  148.  
  149. Property Let HiToLo(fHiToLoA As Boolean)
  150.     fHiToLo = fHiToLoA
  151.     Sort 0, lst.ListCount - 1
  152. End Property
  153.  
  154. Property Get HiToLo() As Boolean
  155.     HiToLo = fHiToLo
  156. End Property
  157.  
  158. Property Let SortMode(esmModeA As ESortMode)
  159.     esmMode = esmModeA
  160.     Sort 0, lst.ListCount - 1
  161. End Property
  162.  
  163. Property Get SortMode() As ESortMode
  164.     SortMode = esmMode
  165. End Property
  166.  
  167. ' Gives away the store for iteration
  168. Property Get Items() As Collection
  169.     Set Items = lst
  170. End Property
  171.  
  172. ' Collection name
  173. Property Get Count() As Integer
  174.     Count = lst.ListCount
  175. End Property
  176.  
  177. ' Index replaces Index property of a control array--if you really need
  178. ' to manage control-array at run-time, use Index of external list box
  179. Property Get Index() As Variant
  180.     LastError = eseNone
  181.     Index = lst.ListIndex + 1
  182. End Property
  183.  
  184. Property Let Index(vIndexA As Variant)
  185.     LastError = eseNone
  186.     If IsNumeric(vIndexA) Then
  187.         lst.ListIndex = vIndexA - 1
  188.     Else
  189.         lst.ListIndex = Match(vIndexA) - 1
  190.     End If
  191.     If lst.ListIndex = -1 Then LastError = eseItemNotFound
  192. End Property
  193.  
  194. Property Get IndexItem() As Variant
  195.     LastError = eseNone
  196.     IndexItem = lst.list(lst.ListIndex)
  197. End Property
  198.  
  199. ' 1-based versions of ItemData
  200. Property Get Data(i As Integer) As Variant
  201.     Data = lst.ItemData(i - 1)
  202. End Property
  203.  
  204. Property Let Data(i As Integer, vDataA As Variant)
  205.     lst.Data(i - 1) = vDataA
  206. End Property
  207.  
  208. Property Get LastErrorStr() As String
  209.     LastErrorStr = asError(LastError)
  210. End Property
  211.  
  212.  
  213. ''' Public Methods From Contained Class '''
  214.     
  215. Sub Clear()
  216.     lst.Clear
  217. End Sub
  218.  
  219. Sub Drag(Optional vAction As Variant)
  220.     If IsMissing(vAction) Then
  221.         lst.Drag
  222.     Else
  223.         lst.Drag vAction
  224.     End If
  225. End Sub
  226.  
  227. Sub Move(x As Variant, Optional y As Variant, Optional dx As Variant, Optional dy As Variant)
  228.     If IsMissing(y) Then
  229.         lst.Move x
  230.     ElseIf IsMissing(dx) Then
  231.         lst.Move x, y
  232.     ElseIf IsMissing(dy) Then
  233.         lst.Move x, y, dx
  234.     Else
  235.         lst.Move x, y, dx, dy
  236.     End If
  237. End Sub
  238.  
  239. Sub Refresh()
  240.     Sort 0, lst.ListCount - 1
  241.     lst.Refresh
  242. End Sub
  243.  
  244. Sub SetFocus()
  245.     lst.SetFocus
  246. End Sub
  247.  
  248. Sub ZOrder(Optional vPosition As Variant)
  249.     If IsMissing(vPosition) Then
  250.         lst.ZOrder
  251.     Else
  252.         lst.ZOrder vPosition
  253.     End If
  254. End Sub
  255.  
  256. ''' Public Properties From Contained Class '''
  257.  
  258. Property Get BackColor() As Long
  259.     BackColor = lst.BackColor
  260. End Property
  261.  
  262. Property Let BackColor(iBackColorA As Long)
  263.     lst.BackColor = iBackColorA
  264. End Property
  265.  
  266. Property Get Columns() As Integer
  267.     Columns = lst.Columns
  268. End Property
  269.  
  270. Property Let Columns(iColumnsA As Integer)
  271.     lst.Columns = iColumnsA
  272. End Property
  273.  
  274. Property Get Enabled() As Boolean
  275.     Enabled = lst.Enabled
  276. End Property
  277.  
  278. Property Let Enabled(fEnabledA As Boolean)
  279.     lst.Enabled = fEnabledA
  280. End Property
  281.  
  282. Property Get ForeColor() As Long
  283.     ForeColor = lst.ForeColor
  284. End Property
  285.  
  286. Property Let ForeColor(iForeColorA As Long)
  287.     lst.ForeColor = iForeColorA
  288. End Property
  289.  
  290. Property Get Height() As Single
  291.     Height = lst.Height
  292. End Property
  293.  
  294. Property Let Height(rHeightA As Single)
  295.     lst.Height = rHeightA
  296. End Property
  297.  
  298. Property Get HelpContextID() As Integer
  299.     HelpContextID = lst.HelpContextID
  300. End Property
  301.  
  302. Property Let HelpContextID(iHelpContextIDA As Integer)
  303.     lst.HelpContextID = iHelpContextIDA
  304. End Property
  305.  
  306. Property Get hWnd() As Integer
  307.     hWnd = lst.hWnd
  308. End Property
  309.  
  310. Property Let hWnd(hWndA As Integer)
  311.     lst.hWnd = hWndA
  312. End Property
  313.  
  314. Property Get ItemData(i As Integer) As Variant
  315.     ItemData = lst.ItemData(i)
  316. End Property
  317.  
  318. Property Let ItemData(i As Integer, vItemDataA As Variant)
  319.     lst.ItemData(i) = vItemDataA
  320. End Property
  321.  
  322. Property Get Left() As Single
  323.     Left = lst.Left
  324. End Property
  325.  
  326. Property Let Left(rLeftA As Single)
  327.     lst.Left = rLeftA
  328. End Property
  329.  
  330. Property Get list(iIndex As Integer) As String
  331.     list = lst.list(iIndex)
  332. End Property
  333.  
  334. Property Let list(iIndex As Integer, sListA As String)
  335.     lst.list(iIndex) = sListA
  336. End Property
  337.  
  338. Property Get ListCount() As Integer
  339.     ListCount = lst.ListCount
  340. End Property
  341.  
  342. Property Get ListIndex() As Integer
  343.     ListIndex = lst.ListIndex
  344. End Property
  345.  
  346. Property Let ListIndex(iListIndexA As Integer)
  347.     lst.ListIndex = iListIndexA
  348. End Property
  349.  
  350. Property Get MousePointer() As Integer
  351.     MousePointer = lst.MousePointer
  352. End Property
  353.  
  354. Property Let MousePointer(iMousePointerA As Integer)
  355.     lst.MousePointer = iMousePointerA
  356. End Property
  357.  
  358. Property Get MultiSelect() As Integer
  359.     MultiSelect = lst.MultiSelect
  360. End Property
  361.  
  362. Property Let MultiSelect(iMultiSelectA As Integer)
  363.     lst.MultiSelect = iMultiSelectA
  364. End Property
  365.  
  366. Property Get NewIndex() As Integer
  367.     NewIndex = lst.NewIndex
  368. End Property
  369.  
  370. Property Get Parent() As Form
  371.     Set Parent = lst.Parent
  372. End Property
  373.  
  374. Property Get Selected(i As Integer) As Boolean
  375.     Selected = lst.Selected(i)
  376. End Property
  377.  
  378. Property Let Selected(i As Integer, fSelectedA As Boolean)
  379.     lst.Selected(i) = fSelectedA
  380. End Property
  381.  
  382. Property Get TabIndex() As Integer
  383.     TabIndex = lst.TabIndex
  384. End Property
  385.  
  386. Property Let TabIndex(iTabIndexA As Integer)
  387.     lst.TabIndex = iTabIndexA
  388. End Property
  389.  
  390. Property Get TabStop() As Boolean
  391.     TabStop = lst.TabStop
  392. End Property
  393.  
  394. Property Let TabStop(fTabStopA As Boolean)
  395.     lst.TabStop = fTabStopA
  396. End Property
  397.  
  398. Property Get Tag() As String
  399.     Tag = lst.Tag
  400. End Property
  401.  
  402. Property Let Tag(sTagA As String)
  403.     lst.Tag = sTagA
  404. End Property
  405.  
  406. Property Get Text() As String
  407.     Text = lst.Text
  408. End Property
  409.  
  410. Property Let Text(sTextA As String)
  411.     lst.Text = sTextA
  412. End Property
  413.  
  414. Property Get Top() As Single
  415.     Top = lst.Top
  416. End Property
  417.  
  418. Property Let Top(rTopA As Single)
  419.     lst.Top = rTopA
  420. End Property
  421.  
  422. Property Get TopIndex() As Integer
  423.     TopIndex = lst.TopIndex
  424. End Property
  425.  
  426. Property Let TopIndex(iTopIndexA As Integer)
  427.     lst.TopIndex = iTopIndexA
  428. End Property
  429.  
  430. Property Get Visible() As Boolean
  431.     Visible = lst.Visible
  432. End Property
  433.  
  434. Property Let Visible(fVisibleA As Boolean)
  435.     lst.Visible = fVisibleA
  436. End Property
  437.  
  438. Property Get Width() As Single
  439.     Width = lst.Width
  440. End Property
  441.  
  442. Property Let Width(rWidthA As Single)
  443.     lst.Width = rWidthA
  444. End Property
  445.  
  446. ''' Private Procedures Used by Class '''
  447.  
  448. Private Function Match(ByVal sItem As String) As Long
  449.     Dim iPos As Integer
  450.     If BSearch(sItem, iPos) Then Match = iPos + 1 Else Match = 0
  451. End Function
  452.  
  453. Private Sub Sort(iFirst As Integer, iLast As Integer)
  454.     Dim vSplit As Variant
  455.     Static fRand As Integer
  456.     If fRand = False Then
  457.         Randomize
  458.         fRand = True
  459.     End If
  460.  
  461.     If iFirst < iLast Then
  462.  
  463.         ' Only two elements in this subdivision. Exchange if
  464.         ' they are out of order, and end recursive calls.
  465.         If iLast - iFirst = 1 Then
  466.             If Compare(lst.list(iFirst), lst.list(iLast)) > 0 Then
  467.                 Swap iFirst, iLast
  468.             End If
  469.         Else
  470.  
  471.             Dim i As Integer, j As Integer, iRand As Integer
  472.  
  473.             ' Pick pivot element at random and move to end
  474.             ' (consider calling Randomize before sorting)
  475.             iRand = GetRandom(iFirst, iLast)
  476.             Swap iLast, iRand
  477.             vSplit = lst.list(iLast)
  478.             Do
  479.  
  480.                 ' Move in from both sides towards the pivot element
  481.                 i = iFirst: j = iLast
  482.                 Do While (i < j) And _
  483.                     Compare(lst.list(i), vSplit) <= 0
  484.                     i = i + 1
  485.                 Loop
  486.                 Do While (j > i) And _
  487.                     Compare(lst.list(j), vSplit) >= 0
  488.                     j = j - 1
  489.                 Loop
  490.  
  491.                 ' If we haven't reached the pivot element, it means
  492.                 ' that two elements on either side are out of order,
  493.                 ' so swap them
  494.                 If i < j Then
  495.                     Swap i, j
  496.                 End If
  497.             Loop While i < j
  498.  
  499.             ' Move the pivot element back to its proper place
  500.             Swap i, iLast
  501.  
  502.             ' Recursively call Sort (pass the smaller
  503.             ' subdivision first to use less stack space)
  504.             If (i - iFirst) < (iLast - i) Then
  505.                 Sort iFirst, i - 1
  506.                 Sort i + 1, iLast
  507.             Else
  508.                 Sort i + 1, iLast
  509.                 Sort iFirst, i - 1
  510.             End If
  511.         End If
  512.     End If
  513.  
  514. End Sub
  515.  
  516. Private Function BSearch(vKey As Variant, iPos As Integer) As Boolean
  517.     Dim iLo As Integer, iHi As Integer, iComp As Integer, iMid As Integer
  518.     iLo = 0: iHi = lst.ListCount - 1
  519.     Do
  520.         iMid = iLo + ((iHi - iLo) \ 2)
  521.         iComp = Compare(lst.list(iMid), vKey)
  522.         Select Case iComp
  523.         Case 0
  524.             ' Item found
  525.             iPos = iMid
  526.             BSearch = True
  527.             Exit Function
  528.         Case Is > 0
  529.             ' Item is in upper half
  530.             iHi = iMid
  531.             If iLo = iHi Then Exit Do
  532.         Case Is < 0
  533.             ' Item is in lower half
  534.             iLo = iMid + 1
  535.             If iLo > iHi Then Exit Do
  536.         End Select
  537.     Loop
  538.     ' Item not found, but return position to insert
  539.     iPos = iMid - (iComp < 0)
  540.     BSearch = False
  541.  
  542. End Function
  543.  
  544. Private Function Compare(v1 As Variant, v2 As Variant) As Integer
  545.     Dim i As Integer
  546.     If IsNumeric(v1) And IsNumeric(v2) Then v1 = Val(v1): v2 = Val(v2)
  547.     
  548.     Select Case esmMode
  549.     ' Sort by value (same as esmSortBin for strings)
  550.     Case esmSortVal
  551.         If v1 < v2 Then
  552.             i = -1
  553.         ElseIf v1 = v2 Then
  554.             i = 0
  555.         Else
  556.             i = 1
  557.         End If
  558.     ' Sort case-insensitive
  559.     Case esmSortText
  560.         i = StrComp(v1, v2, 1)
  561.     ' Sort case-sensitive
  562.     Case esmSortbin
  563.         i = StrComp(v1, v2, 0)
  564.     ' Sort by string length
  565.     Case esmSortLen
  566.         If Len(v1) = Len(v2) Then
  567.             If v1 = v2 Then
  568.                 i = 0
  569.             ElseIf v1 < v2 Then
  570.                 i = -1
  571.             Else
  572.                 i = 1
  573.             End If
  574.         ElseIf Len(v1) < Len(v2) Then
  575.             i = -1
  576.         Else
  577.             i = 1
  578.         End If
  579.     End Select
  580.     If fHiToLo Then i = -i
  581.     Compare = i
  582. End Function
  583.  
  584. Sub Swap(i1 As Integer, i2 As Integer)
  585.     Dim vT As Variant
  586.     vT = lst.list(i1)
  587.     lst.list(i1) = lst.list(i2)
  588.     lst.list(i2) = vT
  589. End Sub
  590.  
  591. Private Sub Class_Initialize()
  592.     Const sNone = ""
  593.     Const sItemNotFound = "Item not found"
  594.     Const sOutOfRange = "Out of range"
  595.     Const sDuplicateNotAllowed = "Duplicate item not allowed"
  596.     Const sNoSelection = "Nothing selected"
  597.     Const sNotListBox = "Lacks list box methods or properties"
  598.     Const sAlreadySorted = "List box already sorted"
  599.     Const sUnknown = "Unknown error"
  600.     
  601.     asError(eseNone) = sNone
  602.     asError(eseItemNotFound) = sItemNotFound
  603.     asError(eseOutOfRange) = sOutOfRange
  604.     asError(eseDuplicateNotAllowed) = sDuplicateNotAllowed
  605.     asError(eseNoSelection) = sNoSelection
  606.     asError(eseNotListBox) = sNotListBox
  607.     asError(eseAlreadySorted) = sAlreadySorted
  608.     asError(eseUnknown) = sUnknown
  609. End Sub
  610.  
  611. Private Sub Class_Terminate()
  612.     Set lst = Nothing
  613. End Sub
  614.